/*
 * ﻿Copyright (C) 2013-2014 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.eatestutil.container.glassfish;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.Level;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract aspect for configuring logging level of JUL logger and handlers attached to SLF4J logger 
 * programmatically when embedded Glassfish is in classpath.<br />
 * Sub-aspect needs to implements <code>{@link #pointcutForScopeOfSlf4jLoggerInitialization()}</code> 
 * pointcut method to define scope of cross cutting the call to SLF4J's <code>LoggerFactory.getLogger</code> 
 * method.<br />
 * With (embedded) Glassfish, it is impossible to bind to other SLF4J binder than JDK14LoggerAdapter 
 * since embedded Glassfish contains SLF4J's JDK14LoggerAdapter.<br />
 * This can be helpful especially for developing EJB tests using embedded Glassfish and cannot 
 * (or don't want to) setup logging.property file for some reason in the config directory where 
 * the domain.xml file resides too.   
 * 
 * @author Arata Y.
 */
@Aspect
public abstract class AbstractSlf4jLoggerConfiguratorOnGlassfishAspect extends Slf4jJulLoggerConfigurator {
	private Logger logger = LoggerFactory.getLogger( getClass());
		protected Logger getLogger() {
			return logger;
		}
	
	/**
	 * Abstract pointcut to define scope of cross cutting the call to to SLF4J's 
	 * <code>LoggerFactory.getLogger</code> method.<br />
	 * 
	 * @see #pointcutAtCallToSlf4jLoggerFactoryGetLogger()
	 * @see #afterReturningInitializationOfSlf4jLogger(JoinPoint, Logger)
	 */
	@Pointcut
	public abstract void pointcutForScopeOfSlf4jLoggerInitialization();
	
	/**
	 * Pointcut for call to SLF4J's <code>LoggerFactory.getLogger</code> method.<br />
	 * 
	 * @see #pointcutForScopeOfSlf4jLoggerInitialization()
	 * @see #afterReturningInitializationOfSlf4jLogger(JoinPoint, Logger)
	 */
	@Pointcut( "call( * org.slf4j.LoggerFactory+.getLogger( ..))")
	public static void pointcutAtCallToSlf4jLoggerFactoryGetLogger() {}

	private Boolean isBoundToJDK14LoggerAdapter = null; 
		/**
		 * Check whether SLF4J logger is bound to <code>JDK14LoggerAdapter</code> adapter.<br />
		 * This also construct set of JUL logging handlers having configured for SLF4J root logger if 
		 * SLF4J logger is bound to <code>JDK14LoggerAdapter</code adapter. The constructed set can be 
		 * obtained by <code>{@link #getHandlerSet()}</code> method.
		 * 
		 * @return boolean value indicating whether SLF4J logger is bound to 
		 * <code>JDK14LoggerAdapter</code> adapter.
		 */
		@Override
		public boolean isBoundToJDK14LoggerAdapter() {
			if ( isBoundToJDK14LoggerAdapter == null) {
				isBoundToJDK14LoggerAdapter = super.isBoundToJDK14LoggerAdapter();
				if ( isBoundToJDK14LoggerAdapter.booleanValue()) {
					java.util.logging.Logger julLogger 
					= getBoundJulLogger( LoggerFactory.getLogger( Logger.ROOT_LOGGER_NAME));
//TODO instead of setting logging level of handlers bound to root logger, clone those handlers, set logging level on those clones, and store to handlerSet member field, in order to avoid messy logs by not-intended loggers.
					for( Handler handler : julLogger.getHandlers()) {
						if ( getLoggerLevel().intValue() < handler.getLevel().intValue()) {
							handler.setLevel( getLoggerLevel());
						}
					}
				}
			}
			return isBoundToJDK14LoggerAdapter;
		}
	
	/**
	 * Override this to return level what suits your preference.<br />
	 * 
	 * @return default to Level.FINER.
	 */
	public Level getLoggerLevel() {
		return Level.FINER;
	}
	
	private static Set<Handler> handlerSet 
	= Collections.synchronizedSet( new LinkedHashSet<Handler>());
		/**
		 * Override this when other JUL logging handler than one having configured for SLF4J root 
		 * logger needs to be bound.<br />
		 * 
		 * @return <code>Set</code> of JUL log handlers to be set to JUL logger bound to SLF4J logger.
		 * @see #isBoundToJDK14LoggerAdapter()
		 * @see #afterReturningInitializationOfSlf4jLogger(JoinPoint, Logger)
		 */
		public Set<Handler> getHandlerSet() {
			return AbstractSlf4jLoggerConfiguratorOnGlassfishAspect.handlerSet;
		}
		public void addHandler( final Handler julHandler) {
			if ( Handler.class.isInstance( julHandler)) getHandlerSet().add( julHandler);
		}
		public void removeHandler( final Handler julHandler) {
			if ( Handler.class.isInstance( julHandler)) getHandlerSet().remove( julHandler);
		}

	/**
	 * Configure logging level of JUL logger bound to SLF4J logger at time of returning from 
	 * call to SLF4J's <code>LoggerFactory.getLogger</code> method.<br />
	 * 
	 * @param joinPoint
	 * @param slf4jLogger
	 * 
	 * @see #pointcutForScopeOfSlf4jLoggerInitialization()
	 * @see #pointcutAtCallToSlf4jLoggerFactoryGetLogger()
	 * @see #getLoggerLevel()
	 */
	@AfterReturning( 
			pointcut = "pointcutForScopeOfSlf4jLoggerInitialization() " +
					"&& pointcutAtCallToSlf4jLoggerFactoryGetLogger()", 
			returning = "slf4jLogger")
	public void afterReturningInitializationOfSlf4jLogger( 
			final JoinPoint joinPoint, final Logger slf4jLogger) {
		if ( !isBoundToJDK14LoggerAdapter()) {
			// SLF4J logger was bound to other than JDK14LoggerAdapter
			return;
		}

		java.util.logging.Logger julLogger = getBoundJulLogger( slf4jLogger);
			Level loggerLevel = getLoggerLevel();
				julLogger.setLevel( loggerLevel);
				
				List<Handler> additionalHandlerList = null;
					Set<Handler> handlerSet = getHandlerSet();
					synchronized( handlerSet) {
						additionalHandlerList 
						= (new ArrayList<Handler>( handlerSet));
					}
				
				Handler[] handlerArray = julLogger.getHandlers();
					for( Handler handler : handlerArray) {
						handler.setLevel( loggerLevel);
						additionalHandlerList.remove( handler);
					}
				if ( additionalHandlerList.size() > 0) {
					for( Handler handler : additionalHandlerList) {
						handler.setLevel( loggerLevel);
						julLogger.addHandler(handler);
					}
				}
	}
}